/* Copyright (c) 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package picasaphotomanager;

import autoUpdate.albumContent.AlbumContentUpdateThread;
import com.google.gdata.client.photos.PicasawebService;
import com.google.gdata.data.Link;
import com.google.gdata.data.MediaContent;
import com.google.gdata.data.media.MediaSource;
import com.google.gdata.data.photos.AlbumEntry;
import com.google.gdata.data.photos.AlbumFeed;
import com.google.gdata.data.photos.CommentEntry;
import com.google.gdata.data.photos.GphotoEntry;
import com.google.gdata.data.photos.GphotoFeed;
import com.google.gdata.data.photos.PhotoEntry;
import com.google.gdata.data.photos.PhotoFeed;
import com.google.gdata.data.photos.TagEntry;
import com.google.gdata.data.photos.UserFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;
import com.google.gdata.util.common.util.Base64;
import configuration.Preferences;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import sync.exceptions.MergeException;
import utilities.IO;

/**
 * This is a simple client that provides high-level operations on the Picasa Web
 * Albums GData API. It can also be used as a command-line application to test
 * out some of the features of the API.
 *
 * 
 */
public class PicasawebClient {

    private static final String API_PREFIX = "https://picasaweb.google.com/data/feed/api/user/";
    private final PicasawebService service;

    /**
     * Constructs a new un-authenticated client.
     */
    public PicasawebClient(PicasawebService service, String proxyHost, String proxyPort, String proxyUsername, String proxyPassword) {
        this(service, null, null);
    }

    /**
     * Constructs a new client with the given username and password.
     */
    public PicasawebClient(PicasawebService service, String uname,
            String passwd) {
        this.service = service;

        Preferences preferences = configuration.ConfigurationManager.GetPreferences();

        //Set up the proxy
        System.setProperty("http.proxyHost", preferences.getHttpProxyHost());
        System.setProperty("http.proxyPort", preferences.getHttpProxyPort());
        System.setProperty("https.proxyHost", preferences.getHttpsProxyHost());
        System.setProperty("https.proxyPort", preferences.getHttpsProxyPort());

        String encoded = Base64.encode((preferences.getProxyUsername() + ":" + preferences.getProxyPassword()).getBytes());
        String base64encodedCredentials = "Basic " + encoded;

        service.getRequestFactory().setPrivateHeader("Proxy-Authorization", base64encodedCredentials);


        if (uname != null && passwd != null) {
            try {
                service.setUserCredentials(uname, passwd);
            } catch (AuthenticationException e) {
                throw new IllegalArgumentException(
                        "Connection to the remote server failed or username is in the incorrect format: " + e.getMessage());
            }
        }
    }

    /**
     * Retrieves the albums for the given user.
     */
    public List<AlbumEntry> getAlbums(String username) throws IOException,
            ServiceException {

        String albumUrl = API_PREFIX + username;
        UserFeed userFeed = getFeed(albumUrl, UserFeed.class);

        List<GphotoEntry> entries = userFeed.getEntries();
        List<AlbumEntry> albums = new ArrayList<AlbumEntry>();
        for (GphotoEntry entry : entries) {
            GphotoEntry adapted = entry.getAdaptedEntry();
            if (adapted instanceof AlbumEntry) {
                albums.add((AlbumEntry) adapted);
            }
        }
        return albums;
    }

    /**
     * Retrieves the albums for the currently logged-in user.  This is equivalent
     * to calling {@link #getAlbums(String)} with "default" as the username.
     */
    public List<AlbumEntry> getAlbums() throws IOException, ServiceException {
        return getAlbums("default");
    }

    /**
     * Retrieves the tags for the given user.  These are tags aggregated across
     * the entire account.
     */
    public List<TagEntry> getTags(String uname) throws IOException,
            ServiceException {
        String tagUrl = API_PREFIX + uname + "?kind=tag";
        UserFeed userFeed = getFeed(tagUrl, UserFeed.class);

        List<GphotoEntry> entries = userFeed.getEntries();
        List<TagEntry> tags = new ArrayList<TagEntry>();
        for (GphotoEntry entry : entries) {
            GphotoEntry adapted = entry.getAdaptedEntry();
            if (adapted instanceof TagEntry) {
                tags.add((TagEntry) adapted);
            }
        }
        return tags;
    }

    /**
     * Retrieves the tags for the currently logged-in user.  This is equivalent
     * to calling {@link #getTags(String)} with "default" as the username.
     */
    public List<TagEntry> getTags() throws IOException, ServiceException {
        return getTags("default");
    }

    /**
     * Retrieves the photos for the given album.
     */
    public List<PhotoEntry> getPhotos(AlbumEntry album, String thumbSize, String imageResolution) throws IOException,
            ServiceException {

        String feedHref = getLinkByRel(album.getLinks(), Link.Rel.FEED);
        String imageResolutionParsed = ("-1".equals(imageResolution)) ? "d" : imageResolution;


        if (feedHref.contains("?")) {
            feedHref += "&imgmax=" + imageResolutionParsed + "&thumbsize=" + thumbSize;
        } else {
            feedHref += "?imgmax=" + imageResolutionParsed + "&thumbsize=" + thumbSize;
        }

        AlbumFeed albumFeed = getFeed(feedHref, AlbumFeed.class);

        List<GphotoEntry> entries = albumFeed.getEntries();
        List<PhotoEntry> photos = new ArrayList<PhotoEntry>();
        for (GphotoEntry entry : entries) {
            GphotoEntry adapted = entry.getAdaptedEntry();
            if (adapted instanceof PhotoEntry) {
                photos.add((PhotoEntry) adapted);
            }
        }
        return photos;
    }

    public MediaSource downloadResource(MediaContent media) throws IOException, ServiceException {
        return service.getMedia(media);
    }

    /**
     * Retrieves the comments for the given photo.
     */
    public List<CommentEntry> getComments(PhotoEntry photo) throws IOException,
            ServiceException {

        String feedHref = getLinkByRel(photo.getLinks(), Link.Rel.FEED);
        AlbumFeed albumFeed = getFeed(feedHref, AlbumFeed.class);

        List<GphotoEntry> entries = albumFeed.getEntries();
        List<CommentEntry> comments = new ArrayList<CommentEntry>();
        for (GphotoEntry entry : entries) {
            GphotoEntry adapted = entry.getAdaptedEntry();
            if (adapted instanceof CommentEntry) {
                comments.add((CommentEntry) adapted);
            }
        }
        return comments;
    }

    /**
     * Retrieves the tags for the given taggable entry.  This is valid on user,
     * album, and photo entries only.
     */
    public List<TagEntry> getTags(GphotoEntry<?> parent) throws IOException,
            ServiceException {

        String feedHref = getLinkByRel(parent.getLinks(), Link.Rel.FEED);
        feedHref = addKindParameter(feedHref, "tag");
        AlbumFeed albumFeed = getFeed(feedHref, AlbumFeed.class);

        List<GphotoEntry> entries = albumFeed.getEntries();
        List<TagEntry> tags = new ArrayList<TagEntry>();
        for (GphotoEntry entry : entries) {
            GphotoEntry adapted = entry.getAdaptedEntry();
            if (adapted instanceof TagEntry) {
                tags.add((TagEntry) adapted);
            }
        }
        return tags;
    }

    /**
     * Album-specific insert method to insert into the gallery of the current
     * user, this bypasses the need to have a top-level entry object for parent.
     */
    public AlbumEntry insertAlbum(AlbumEntry album)
            throws IOException, ServiceException {
        String feedUrl = API_PREFIX + "default";
        return service.insert(new URL(feedUrl), album);
    }

    /**
     * Insert an entry into another entry.  Because our entries are a hierarchy,
     * this lets you insert a photo into an album even if you only have the
     * album entry and not the album feed, making it quicker to traverse the
     * hierarchy.
     */
    public <T extends GphotoEntry> T insert(GphotoEntry<?> parent, T entry)
            throws IOException, ServiceException {

        String feedUrl = getLinkByRel(parent.getLinks(), Link.Rel.FEED);
        return service.insert(new URL(feedUrl), entry);
    }

    /**
     * Helper function to allow retrieval of a feed by string url, which will
     * create the URL object for you.  Most of the Link objects have a string
     * href which must be converted into a URL by hand, this does the conversion.
     */
    public <T extends GphotoFeed> T getFeed(String feedHref,
            Class<T> feedClass) throws IOException, ServiceException {
        System.out.println("Get Feed URL: " + feedHref);
        return service.getFeed(new URL(feedHref), feedClass);
    }

    /**
     * Helper function to add a kind parameter to a url.
     */
    public String addKindParameter(String url, String kind) {
        if (url.contains("?")) {
            return url + "&kind=" + kind;
        } else {
            return url + "?kind=" + kind;
        }
    }

    /**
     * Helper function to get a link by a rel value.
     */
    public String getLinkByRel(List<Link> links, String relValue) {
        for (Link link : links) {
            if (relValue.equals(link.getRel())) {
                return link.getHref();
            }
        }
        throw new IllegalArgumentException("Missing " + relValue + " link.");
    }

    public void deleteMedia(String mediaUrlString) {
        try {
            PhotoFeed photoFeed = getFeed(mediaUrlString, PhotoFeed.class);


            URL mediaUrl = new URL(mediaUrlString);
            service.delete(mediaUrl);
        } catch (ServiceException ex) {
            Logger.getLogger(PicasawebClient.class.getName()).log(Level.SEVERE, null, ex);
        } catch (MalformedURLException ex) {
            Logger.getLogger(PicasawebClient.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(PicasawebClient.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static String getHashForUrlResource(InputStream in, long contentLength) throws MergeException {
        try {
            URL mediaUrl = new URL(MediaUrlString);

            URLConnection connection = mediaUrl.openConnection();
            InputStream stream = mediaUrl.openStream();
            int contentLength = connection.getContentLength();
            byte[] buffor = new byte[contentLength];
            int bytesRead = 0;
            int offset = 0;

            while (offset < contentLength) {
                bytesRead = stream.read(buffor, offset, contentLength - offset);

                if (bytesRead == -1) {
                    break;
                }

                offset += bytesRead;
            }

            Logger.getLogger(PicasawebClient.class.getName()).log(Level.FINEST, "Hash image size: {0} bytes for URL {1}", new Object[]{buffor.length, MediaUrlString});

            MessageDigest hashGenerator = MessageDigest.getInstance("MD5");
            return IO.byteArrayToHexString(hashGenerator.digest(buffor));
        } catch (NoSuchAlgorithmException ex) {
            throw new MergeException("MD5 hashing algorithm is not supported on your platform.", ex);
        } catch (MalformedURLException ex) {
            throw new MergeException("The resource URL is in the incorrect format.", ex);
        } catch (IOException ex) {
            throw new MergeException("There was a problem reading data from the remote location.", ex);
        }
    }

    public static Boolean downloadToFile(File saveFile, URL url, boolean overwrite) {

        InputStream input = null;
        byte[] buffor = null;

        //check if the file already exists
        if (saveFile.exists() && !overwrite) {
            return false;
        }


        try {
            URLConnection connection = url.openConnection();
            int contentLength = connection.getContentLength();
            buffor = new byte[contentLength];

            InputStream raw = url.openStream();
            input = new BufferedInputStream(raw);

            int offset = 0;
            int bytesRead = 0;

            while (offset < contentLength) {
                bytesRead = input.read(buffor, offset, contentLength - offset);

                if (bytesRead == -1) {
                    break;
                }
                offset += bytesRead;

            }

        } catch (IOException ex) {
            Logger.getLogger(AlbumContentUpdateThread.class.getName()).log(Level.SEVERE, "Error while reading the data from server: ", ex);
            return false;
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException ex) {
                    Logger.getLogger(AlbumContentUpdateThread.class.getName()).log(Level.SEVERE, "Cannot close the stream when downloading data from the server: ", ex);
                }
            }
        }

        FileOutputStream fileStream = null;

        try {
            fileStream = new FileOutputStream(saveFile);
            fileStream.write(buffor);
            fileStream.flush();
            return true;
        } catch (FileNotFoundException ex) {
            Logger.getLogger(AlbumContentUpdateThread.class.getName()).log(Level.SEVERE, "File " + saveFile + " not found: ", ex);
            return false;
        } catch (IOException ex) {
            Logger.getLogger(AlbumContentUpdateThread.class.getName()).log(Level.SEVERE, "Error while saving file" + saveFile + ": ", ex);
            return false;
        } finally {
            if (fileStream != null) {
                try {
                    fileStream.close();
                } catch (IOException ex) {
                    Logger.getLogger(AlbumContentUpdateThread.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }
}
